#type vertex
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNor;
layout (location = 2) in vec2 aTex;

out vec3 FragPos;
out vec3 FragNor;
out vec2 FragTex;

uniform mat4 _model;
uniform mat4 _view;
uniform mat4 _projection;

void main()
{
    FragPos = (_model * vec4(aPos, 1.0)).xyz;
    FragNor = mat3(transpose(inverse(_model))) * aNor;
    FragTex = aTex;
    gl_Position = _projection * _view * _model * vec4(aPos, 1.0);
}

#type fragment
#version 330 core

in vec3 FragPos;
in vec3 FragNor;
in vec2 FragTex;

out vec4 FragColor;

////////////////
// Material
////////////////

uniform vec3 _eye_pos;

struct Material
{
    sampler2D diffuse;
    sampler2D specular;
    sampler2D emission;
    int shininess;
};

uniform Material _material;

////////////////
// Light
////////////////

#define DIRECTION_LIGHT_MAX 3
struct DirectionLight
{
    float intensity;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    vec3 direction;
};
uniform int _direct_light_nums;
uniform DirectionLight _direct_lights[DIRECTION_LIGHT_MAX];

#define POINT_LIGHT_MAX 5
struct PointLight
{
    float intensity;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    vec3 position;
    float constant;
    float linear;
    float quadratic;
};
uniform int _point_light_nums;
uniform PointLight _point_lights[POINT_LIGHT_MAX];

#define SPOT_LIGHT_MAX 5
struct SpotLight
{
    float intensity;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    vec3 position;
    float constant;
    float linear;
    float quadratic;
    vec3 direction;//  direction of the spoting cone
    float innerCutOff;//  inner cut off
    float outerCutOff;//  outer cut off
};
uniform int _spot_light_nums;
uniform SpotLight _spot_lights[SPOT_LIGHT_MAX];

vec3 phong_diffuse(vec3 diffuse_para, vec3 normal_para, vec3 light_dir_para)
{
    float diff = max(dot(normal_para, light_dir_para), 0.0);
    return diffuse_para * diff * vec3(texture(_material.diffuse, FragTex));
}

vec3 phong_specular(vec3 specular_para, vec3 normal_para, vec3 light_dir_para, vec3 eye_dir_para)
{
    vec3 reflectDir = reflect(-light_dir_para, normal_para);
    float spec = pow(max(dot(eye_dir_para, reflectDir), 0.0), _material.shininess);
    return specular_para * spec * vec3(texture(_material.specular, FragTex));
}

void main()
{
    vec3 light_dir;
    vec3 normal = normalize(FragNor);
    vec3 eye_dir = normalize(_eye_pos - FragPos);
    vec3 result = vec3(0.0);
    vec3 ambient, diffuse, specular;

    for (int i = 0; i < _direct_light_nums && i < DIRECTION_LIGHT_MAX; i++)
    {
        light_dir = normalize(_direct_lights[i].direction);
        ambient = _direct_lights[i].ambient * vec3(texture(_material.diffuse, FragTex));
        diffuse = phong_diffuse(_direct_lights[i].diffuse, normal, light_dir);
        specular = phong_specular(_direct_lights[i].specular, normal, light_dir, eye_dir);
        result += ambient + _direct_lights[i].intensity * (diffuse + specular);
    }

    for (int i = 0; i < _point_light_nums && i < POINT_LIGHT_MAX; i++)
    {
        vec3 light_distance = _point_lights[i].position - FragPos;
        float distance = length(light_distance);
        light_dir = normalize(light_distance);
        float attenuation = 1.0 / (_point_lights[i].constant + _point_lights[i].linear * distance
        + _point_lights[i].quadratic * (distance * distance));
        ambient = _point_lights[i].ambient * vec3(texture(_material.diffuse, FragTex));
        diffuse = phong_diffuse(_point_lights[i].diffuse, normal, light_dir);
        specular = phong_specular(_point_lights[i].specular, normal, light_dir, eye_dir);
        result += attenuation * (ambient + _point_lights[i].intensity * (diffuse + specular));
    }

    for (int i = 0; i < _spot_light_nums && i < SPOT_LIGHT_MAX; i++)
    {
        vec3 light_distance = _spot_lights[i].position - FragPos;
        float distance = length(light_distance);
        light_dir = normalize(light_distance);
        float attenuation = 1.0 / (_spot_lights[i].constant + _spot_lights[i].linear * distance
        + _spot_lights[i].quadratic * (distance * distance));
        ambient = _spot_lights[i].ambient * vec3(texture(_material.diffuse, FragTex));
        diffuse = phong_diffuse(_spot_lights[i].diffuse, normal, light_dir);
        specular = phong_specular(_spot_lights[i].specular, normal, light_dir, eye_dir);
        float theta = dot(light_dir, normalize(_spot_lights[i].direction));
        float intensity =
        clamp((theta - _spot_lights[i].outerCutOff) /
        (_spot_lights[i].innerCutOff - _spot_lights[i].outerCutOff),
        0.0, 1.0);
        result += attenuation * (ambient + _spot_lights[i].intensity * intensity * (diffuse + specular));
    }

//    result += vec3(texture(_material.emission, FragTex));//  emission

    FragColor = vec4(result, 1.0);

}